package com.xuecheng.content.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xuecheng.api.content.model.dto.CourseBaseDTO;
import com.xuecheng.api.content.model.dto.TeachplanDTO;
import com.xuecheng.api.content.model.dto.TeachplanMediaDTO;
import com.xuecheng.api.media.dto.MediaDTO;
import com.xuecheng.content.agent.MediaApiAgent;
import com.xuecheng.content.common.domain.code.CommonErrorCode;
import com.xuecheng.content.common.domain.code.ContentErrorCode;
import com.xuecheng.content.common.domain.response.RestResponse;
import com.xuecheng.content.common.enums.common.AuditEnum;
import com.xuecheng.content.common.enums.common.CommonEnum;
import com.xuecheng.content.common.enums.content.CourseAuditEnum;
import com.xuecheng.content.common.enums.content.CourseModeEnum;
import com.xuecheng.content.common.enums.content.TeachPlanEnum;
import com.xuecheng.content.common.exception.ExceptionCast;
import com.xuecheng.content.common.util.StringUtil;
import com.xuecheng.content.convert.TeachplanConvert;
import com.xuecheng.content.convert.TeachplanMediaConvert;
import com.xuecheng.content.entity.CourseBase;
import com.xuecheng.content.entity.Teachplan;
import com.xuecheng.content.entity.TeachplanMedia;
import com.xuecheng.content.entity.ext.TeachplanNode;
import com.xuecheng.content.mapper.TeachplanMapper;
import com.xuecheng.content.service.CourseBaseService;
import com.xuecheng.content.service.TeachplanMediaService;
import com.xuecheng.content.service.TeachplanService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 * 课程计划 服务实现类
 * </p>
 *
 * @author itcast
 */
@Slf4j
@Service
public class TeachplanServiceImpl extends ServiceImpl<TeachplanMapper, Teachplan> implements TeachplanService {


    @Autowired
    private CourseBaseService courseBaseService;

    @Autowired
    private TeachplanMediaService mediaService;


    @Autowired
    private MediaApiAgent mediaApiAgent;


    @Autowired
    private TeachplanMediaService teachplanMediaService;


    /*
     * 业务分析：
     *  1.判断关键数据：操作时的必要数据（一般是由前端来传递）
     *       courseId  companyId
     *  2.判断业务数据：操作时，判断数据的状态（一般是判断数据值，数据值来源于后端的数据源）
     *     课程基本信息
     *           判断是否是同一家教学机构
     *           判断课程是否删除
     *
     *  3.获得课程下的所有课程计划数据
     *  4.通过java的递归来生成课程计划树形结构
     *  5.将node数据转为dto并返回
     *
     * */

    @Override
    public TeachplanDTO queryTreeNodesByCourseId(Long courseId, long companyId) {

        // 1.判断关键数据
        //      courseId companyId
        if (ObjectUtils.isEmpty(courseId)||
                ObjectUtils.isEmpty(companyId)
        ) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }

        //  2.判断业务数据
        //      课程基础信息
        //          判断是否存在
        //          判断是否是同一家教学机构
        //          判断是否删除
        LambdaQueryWrapper<CourseBase> baseQueryWrapper = new LambdaQueryWrapper<>();
        baseQueryWrapper.eq(CourseBase::getId, courseId);
        baseQueryWrapper.eq(CourseBase::getCompanyId, companyId);

        CourseBase courseBase = courseBaseService.getOne(baseQueryWrapper);

        if (ObjectUtils.isEmpty(courseBase)) {
            ExceptionCast.cast(ContentErrorCode.E_120013);
        }

        Integer status = courseBase.getStatus();
        if (!(CommonEnum.USING_FLAG.getCodeInt().equals(status))) {
            ExceptionCast.cast(ContentErrorCode.E_120019);
        }

        //  3.根据Courseid查询课程计划数据
        TeachplanMapper baseMapper = this.getBaseMapper();
        List<TeachplanNode> nodes = baseMapper.selectByCourseId(courseId);


        //  4.通过java的递归生成课程计划树形结构

        TeachplanDTO teachplanDTO = null;
        if (CollectionUtils.isEmpty(nodes)) {
            teachplanDTO = new TeachplanDTO();
        } else {

            TeachplanNode rootNode = nodes.remove(0);

            generateTreeNodes(rootNode,nodes);

            teachplanDTO = TeachplanConvert.INSTANCE.node2dto(rootNode);
        }


        //  5.将数据转为dto并返回
        return teachplanDTO;
    }
    /*
    树形结构方法
     */
    private void generateTreeNodes(TeachplanNode parentNode, List<TeachplanNode> nodes) {

        // 1.判断父级集合是否为空，如果为空需要创建
        if (CollectionUtils.isEmpty(parentNode.getChildrenNodes())) {
            parentNode.setChildrenNodes(new ArrayList<>(8));
        }

        // 2.循环遍历集合生成树形结构
        for (TeachplanNode node : nodes) {

            // 2.1 判断node是否和parentnode有关联，如果有关联将node存放到parentNode集合中
            if (ObjectUtils.nullSafeEquals(parentNode.getId(),node.getParentid())) {

                parentNode.getChildrenNodes().add(node);

                // 2.2 生成node集合数据
                // 如果是第三级课程计划，无需递归
                if (!(TeachPlanEnum.THIRD_LEVEL.equals(node.getGrade()))) {
                    generateTreeNodes(node,nodes);
                }
            }
        }

    }

    /*
    新增和修改课程计划信息
    业务分析
    1.判断是新增还是修改：teachplanId
    如果有修改，如果没就是创建
    2.执行创建和修改操作，并返回
     */
    @Override
    @Transactional
    public TeachplanDTO createOrModifyTeachPlan(TeachplanDTO dto, Long companyId) {

        TeachplanDTO resutlDTO = null;
        //1.判断业务操作
        //     判断是创建还是修改：teachplanId
        //     如果有:修改
        //     如果没有:创建
        // 2.执行创建和修改操作并返回dto数据
        if (ObjectUtils.isEmpty(dto.getTeachPlanId())) {
            resutlDTO = createTeachplan(dto, companyId);
        } else {
            resutlDTO = modifyTeachplan(dto, companyId);
        }

        // 3.返回结果数据dto
        return resutlDTO;
    }


    /*
    修改
     新增课程计划信息
 1.判断关键数据
 2.判断业务数据
     课程基础信息
         判断是否存在
         判断同一家机构
         判断是否删除
         判断课程审核状态：未提交和审核未通过
         課程計劃，
         判斷是否存在，
  3.修改課程計劃數據
        pname，startime  endtime is_preview
  4.判斷修改計劃數據
  5.將數據庫最新的數據轉爲dto并返回

     */
    private TeachplanDTO modifyTeachplan(TeachplanDTO dto, Long companyId) {
        // 1.判断关键数据
        //    2.判断业务数据
        //        课程基础信息
        //            判断是否存在
        //            判断同一家机构
        //            判断是否删除
        //            判断课程审核状态：未提交和审核未通过

        CourseBaseDTO baseDTO = verifyTeachplanMsg(dto, companyId);

        // 課程計劃，
        //         判斷是否存在，
//        Teachplan teachplan = this.getById(dto.getTeachPlanId());
//        用不到上面的
        LambdaQueryWrapper<Teachplan> teachplanQueryWrapper = new LambdaQueryWrapper<>();
        teachplanQueryWrapper.eq(Teachplan::getId,dto.getTeachPlanId());
        int count = this.count(teachplanQueryWrapper);
        if (count<1){
            ExceptionCast.cast(ContentErrorCode.E_120402);
        }



        //3.修改課程計劃數據
        //        pname，startime  endtime is_preview
        LambdaUpdateWrapper<Teachplan> teachplanLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        teachplanLambdaUpdateWrapper.set(Teachplan::getPname, dto.getPname());
        teachplanLambdaUpdateWrapper.set(Teachplan::getStartTime, dto.getStartTime());
        teachplanLambdaUpdateWrapper.set(Teachplan::getEndTime, dto.getEndTime());
        teachplanLambdaUpdateWrapper.set(Teachplan::getIsPreview, dto.getIsPreview());

        teachplanLambdaUpdateWrapper.eq(Teachplan::getId, dto.getTeachPlanId());
        //4.判斷修改計劃數據
        boolean update = this.update(teachplanLambdaUpdateWrapper);

        if (!update){
            ExceptionCast.cast(ContentErrorCode.E_120017);
        }
//        5.將數據庫最新的數據轉爲dto并返回

        Teachplan po = this.getById(dto.getTeachPlanId());
        TeachplanDTO teachplanDTO = TeachplanConvert.INSTANCE.entity2dto(po);

        return teachplanDTO;
    }


    /*
    新增课程计划信息
    1.判断关键数据
    2.判断业务数据
        课程基础信息
            判断是否存在
            判断同一家机构
            判断是否删除
            判断课程审核状态：未提交和审核未通过
            、
     3.获得父级数据并进行关联
        parentid
        grade: 子集的grade是父级的grade+1
        orderby：获得父级课程计划的个数+1（bug）
     4.将dto转换为po并保存
     5.判断保存后的结果
     6.将数据最新的数据转化为dto并返回
     */
    private TeachplanDTO createTeachplan(TeachplanDTO dto, Long companyId) {

        CourseBaseDTO courseBase = verifyTeachplanMsg(dto, companyId);


        // 3.获得父级数据并进行关联
        //     parentid
        //     grade：子级grade是父级grade+1
        //     orderby：获得父级课程计划的子级数据个数+1（bug）
        Teachplan teachplan = TeachplanConvert.INSTANCE.dto2entity(dto);

        Teachplan parentNode =  generateTreeNode(dto,courseBase);

        teachplan.setParentid(parentNode.getId());
        teachplan.setGrade(parentNode.getGrade()+1);

        // select count(1) from teachplan where parentId = parentNode.getId();
        LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(Teachplan::getParentid, parentNode.getId());

        int count = this.count(queryWrapper);
        teachplan.setOrderby(count+1);

        // 4.将dto转为po并保存
        boolean result = this.save(teachplan);

        // 5.判断保存后的结果
        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120407);
        }


        // 6.将数据库最新数据转为dto并返回
        Teachplan po = this.getById(teachplan.getId());

        TeachplanDTO resultDTO = TeachplanConvert.INSTANCE.entity2dto(po);

        return resultDTO;

    }

    private CourseBaseDTO verifyTeachplanMsg(TeachplanDTO dto, Long companyId) {
        //1.判断关键数据
        //1.1判断前端传入的数据
        if (ObjectUtils.isEmpty(companyId)
                //名字不能为空
                || StringUtil.isBlank(dto.getPname())
                //课程的id不能为空
                || ObjectUtils.isEmpty(dto.getCourseId())) {
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        //2.判断业务数据
        //        课程基础信息
        //            判断是否存在
        //            判断同一家机构
        //            判断是否删除
        CourseBaseDTO courseBase = courseBaseService.getCourseBase(dto.getCourseId(), companyId);

        //审核状态
        //运营平台只能给课程审核状态赋值：审核通过、审核未通过

        String auditStatus = dto.getAuditStatus();
        if (CourseAuditEnum.AUDIT_PUBLISHED_STATUS.equals(auditStatus)
                || CourseAuditEnum.AUDIT_COMMIT_STATUS.equals(auditStatus)
                || CourseAuditEnum.AUDIT_UNPAST_STATUS.equals(auditStatus)) {
            ExceptionCast.cast(ContentErrorCode.E_120016);
        }
        return courseBase;
    }

   /*
   业务分析
   1.判断新增数据是2级还是3级课程计划
        根据是否有parentId值
        如果有：三级课程计划
        如果没有：二级课程计划
   2.获得二级课程计划的父级数据
        查询数据库获得一级课程计划数据：courseId，parentid=0
                如果没有自动创建并保存---数据的来源coursebase
                如果有，直接返回
   3.获得三级课程计划的父级数据
        查询数据库获得二级课程计划，parentid
                如果没有，抛出异常
                如果有，直接返回

    */



    private Teachplan generateTreeNode(TeachplanDTO dto, CourseBaseDTO courseBase) {
        //1.判断新增数据是2级还是3级课程计划
        //    根据是否有parentId值
        //     如果有：三级课程计划
        //     如果没有：二级课程计划
        Long parentid = dto.getParentid();
        if (ObjectUtils.isEmpty(parentid)){
            // 2.获得二级课程计划的父级数据
            //        查询数据库获得一级课程计划数据：courseId，parentid=0
            //                如果没有自动创建并保存---数据的来源coursebase
            LambdaQueryWrapper<Teachplan> teachpQueryWrapper = new LambdaQueryWrapper<>();
            teachpQueryWrapper.eq(Teachplan::getCourseId,courseBase.getCourseBaseId());

            teachpQueryWrapper.eq(Teachplan::getParentid,TeachPlanEnum.FIRST_PARENTID_FLAG);

            //查询,获得yiji课程计划
            Teachplan root = this.getOne(teachpQueryWrapper);

            //2.获得二级课程计划的父级数据
            //     查询数据库获得一级课程计划数据：courseId，parentid=0
            //判断一级课程计划
            //             如果没有自动创建并保存---数据的来源coursebase
            //如果有，直接返回
            if (ObjectUtils.isEmpty(root)){
                root = new Teachplan();
                root.setPname(courseBase.getName());
                root.setParentid(new Long(TeachPlanEnum.FIRST_PARENTID_FLAG));
                root.setGrade(TeachPlanEnum.FIRST_LEVEL);
                root.setDescription(courseBase.getDescription());
                root.setOrderby(TeachPlanEnum.FIRST_LEVEL);
                root.setCourseId(courseBase.getCourseBaseId());

                boolean save = this.save(root);
                if (!save){
                    ExceptionCast.cast(ContentErrorCode.E_120417);
                }

            }
            //如果有，直接返回
            return root;

        }else {
            //3.获得三级课程计划的父级数据
            //        查询数据库获得二级课程计划，parentid
            //                如果没有，抛出异常
            //                如果有，直接返回

            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getId,parentid);

            Teachplan teachplan = this.getOne(queryWrapper);

            if (ObjectUtils.isEmpty(teachplan)){
                ExceptionCast.cast(ContentErrorCode.E_120408);
            }
            return teachplan;


        }
    }



    /*
    删除课程计划
     */
    @Override
    public void removeTeachPlan(Long teachPlanId, Long companyId) {

        //1.判断关键数据的合法性
        Teachplan teachplan = getById(teachPlanId);

        if (ObjectUtils.isEmpty(teachplan)) ExceptionCast.cast(ContentErrorCode.E_120402);

        //1.2 判断课程基本信息是否存在
        CourseBase courseBase = courseBaseService.getById(teachplan.getCourseId());

        if (ObjectUtils.isEmpty(courseBase))
            ExceptionCast.cast(ContentErrorCode.E_120013);

        //1.3 判断公司 Id是否和课程中的公司Id一致
        if (!(ObjectUtils.nullSafeEquals(courseBase.getCompanyId(),companyId)))
            ExceptionCast.cast(CommonErrorCode.E_100108);


        // 2. 根据课程计划等级进行业务判断
        if (teachplan.getGrade() == TeachPlanEnum.SECEND_LEVEL) {

            // 判断二级课程计划是否有子级课程计划信息
            LambdaQueryWrapper<Teachplan> queryWrapper = new LambdaQueryWrapper<>();
            queryWrapper.eq(Teachplan::getParentid, teachPlanId);
            int count = count(queryWrapper);


            if (count > 0)
                ExceptionCast.cast(ContentErrorCode.E_120409);


        } else {

            // 判断三级课程计划是否关联课程媒资信息
            LambdaQueryWrapper<TeachplanMedia>
                    mediaLambdaQueryWrapper = new LambdaQueryWrapper<>();

            mediaLambdaQueryWrapper.eq(TeachplanMedia::getTeachplanId,teachPlanId);

            int mediaCount = mediaService.count(mediaLambdaQueryWrapper);

            if (mediaCount > 0) ExceptionCast.cast(ContentErrorCode.E_120413);

        }
        // 4.根据Id删除课程计划
        removeById(teachPlanId);
    }

    /*
    课程计划绑定媒资信息
            1.判断关键数据
                teachplanid mediaid
            2.判断业务数据
                课程计划数据
                    判断是否存在
                    判断课程计划是否是第三级
                    判断是否是同一家机构的操作
                        根据课程基础信息来查询
                        判断课程类型是否是点播
                媒资数据
                    判断是否存在
                    判断是否是同一家机构
                    判断媒资信息是否审核通过
            3.保存课程计划和媒资信息关联关系
                判断关联数据是否存在
                    如果不存在
                        创建数据
                    如果存在
                        修改数据
                            mediaid 和 medianame
            4.将数据封装成dto并返回
     */
    //1111111111
    @Override
    public TeachplanMediaDTO associateMedia(TeachplanMediaDTO dto, Long companyId){
        //  1.判断关键数据
        //        teachplanid mediaid
        if(ObjectUtils.isEmpty(dto)||ObjectUtils.isEmpty(companyId)){
            ExceptionCast.cast(CommonErrorCode.E_100101);
        }
        // 2.判断业务数据
        //                课程计划数据
        //                    判断是否存在
        //                    判断课程计划是否是第三级
        //                    判断是否是同一家机构的操作
        //                        根据课程基础信息来查询
        //                        判断课程类型是否是点播
        LambdaQueryWrapper<Teachplan> teachpQueryWrapper = new LambdaQueryWrapper<>();
        teachpQueryWrapper.eq(Teachplan::getId,dto.getTeachplanId());
        //判断是否等于课程第三级
        teachpQueryWrapper.eq(Teachplan::getGrade,TeachPlanEnum.THIRD_LEVEL);
        Teachplan teachplan = this.getOne(teachpQueryWrapper);
        //判断是否存在
        if (ObjectUtils.isEmpty(teachplan)){
            ExceptionCast.cast(ContentErrorCode.E_120401);
        }
        //判断是否是同一家机构的操作
        //        根据课程基础信息来查询
        //        判断课程类型是否是点播
        LambdaQueryWrapper<CourseBase> courseBaseQueryWrapper = new LambdaQueryWrapper<>();
        courseBaseQueryWrapper.eq(CourseBase::getId,teachplan.getCourseId());
        courseBaseQueryWrapper.eq(CourseBase::getCompanyId,companyId);
        courseBaseQueryWrapper.eq(CourseBase::getTeachmode, CourseModeEnum.COURSE_MODE_RECORD_STATUS.getCode());
        courseBaseQueryWrapper.eq(CourseBase::getStatus,CommonEnum.USING_FLAG.getCodeInt());

        CourseBase courseBase = courseBaseService.getOne(courseBaseQueryWrapper);

        // 课程计划和媒资信息绑定要求课程审核状态为：未提交和审核未通过
        String auditStatus = courseBase.getAuditStatus();

        System.out.println(auditStatus);
        if (CourseAuditEnum.AUDIT_PASTED_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_COMMIT_STATUS.getCode().equals(auditStatus)||
                CourseAuditEnum.AUDIT_PUBLISHED_STATUS.getCode().equals(auditStatus)
        ) {
            ExceptionCast.cast(ContentErrorCode.E_120015);
        }


        //                媒资数据
        //                    判断是否存在
        //                    判断是否是同一家机构
        //                    判断媒资信息是否审核通过
        RestResponse<MediaDTO> restResponse = mediaApiAgent.getMediaById4s(dto.getMediaId());
        //远程调用内容管理对媒资的 Feign 的远程调用，判断远程调用是否成功
        if (!(restResponse.isSuccessful())) {
            ExceptionCast.castWithCodeAndDesc(restResponse.getCode(),restResponse.getMsg());
        }
        //获得媒资响应的数据
        MediaDTO mediaDTO = restResponse.getResult();
        if (!(ObjectUtils.nullSafeEquals(mediaDTO.getCompanyId(),companyId))) {
            ExceptionCast.cast(CommonErrorCode.E_100108);
        }
        String mediaAuditStatus = mediaDTO.getAuditStatus();

        if (!(AuditEnum.AUDIT_PASTED_STATUS.getCode().equals(mediaAuditStatus))) {
            ExceptionCast.cast(ContentErrorCode.E_120416);
        }
        // 3.保存课程计划和媒资信息关联关系
        //                判断关联数据是否存在
        LambdaQueryWrapper<TeachplanMedia> teachplanMediaQueryWrapper = new LambdaQueryWrapper<>();
        teachplanMediaQueryWrapper.eq(TeachplanMedia::getTeachplanId,teachplan.getId());
        teachplanMediaQueryWrapper.eq(TeachplanMedia::getCourseId,teachplan.getCourseId());
        int count = teachplanMediaService.count(teachplanMediaQueryWrapper);

        boolean result = false;
        if (count<1){
            // 如果不存在
            //     创建数据
            TeachplanMedia teachplanMedia = new TeachplanMedia();
            teachplanMedia.setMediaId(mediaDTO.getId());
            teachplanMedia.setTeachplanId(teachplan.getId());
            teachplanMedia.setCourseId(teachplan.getCourseId());
            teachplanMedia.setMediaFilename(mediaDTO.getFilename());

            result = teachplanMediaService.save(teachplanMedia);
        }else {
            //                    如果存在
            //                        修改数据
            //                            mediaid 和 medianame
            LambdaUpdateWrapper<TeachplanMedia> updateWrapper = new LambdaUpdateWrapper<>();
            updateWrapper.set(TeachplanMedia::getMediaId, mediaDTO.getId());
            updateWrapper.set(TeachplanMedia::getMediaFilename, mediaDTO.getFilename());
            updateWrapper.eq(TeachplanMedia::getTeachplanId, teachplan.getId());

            result = teachplanMediaService.update(updateWrapper);
        }

        if (!result) {
            ExceptionCast.cast(ContentErrorCode.E_120411);
        }
        //  4.将数据封装成dto并返回
        LambdaQueryWrapper<TeachplanMedia> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TeachplanMedia::getTeachplanId, teachplan.getId());

        TeachplanMedia teachplanMedia = teachplanMediaService.getOne(queryWrapper);

        TeachplanMediaDTO resultDTO = TeachplanMediaConvert.INSTANCE.entity2dto(teachplanMedia);


        //4.将数据封装成dto并返回
        return resultDTO;
    }








    //===================方法==============
    // 2.判断业务数据：操作时，判断数据的状态（一般是判断数据值，数据值来源于后端的数据源）
    //        //    课程基本信息
    //        //          判断是否是同一家教学机构
    //        //          判断课程是否删除
    private void getByCourseId(Long courseId, long companyId) {
        //课程基础信息
        //          判断是否存在
        LambdaQueryWrapper<CourseBase> baseQueryWrapper = new LambdaQueryWrapper<>();
        //培训机构的id
        baseQueryWrapper.eq(CourseBase::getCompanyId, companyId);
        //课程的id
        baseQueryWrapper.eq(CourseBase::getId, courseId);
        CourseBase courseBase1 = courseBaseService.getOne(baseQueryWrapper);
        // 判断是否是同一家机构
        if (!(ObjectUtils.nullSafeEquals(companyId, courseBase1.getCompanyId()))) {
            ExceptionCast.cast(CommonErrorCode.E_100108);
        }
//        判断课程是否存在
        if (ObjectUtils.isEmpty(courseBase1)){
            ExceptionCast.cast(ContentErrorCode.E_120013);
        }
        //判断是否删除
        Integer status = courseBase1.getStatus();
        if (!CommonEnum.USING_FLAG.getCodeInt().equals(status)){
            ExceptionCast.cast(ContentErrorCode.E_1200503);
        }
    }
}
